package MySQL::Query::Extract::FROM;

use strict;
use warnings;

use base qw(MySQL::Query::Extract);

use Carp;
use Data::Dumper;
use SQL::Builder::Join;
use SQL::Builder::JoinGroup;
use SQL::Builder::FromList;
use SQL::Builder::Using;

my @join_types = qw(LEFT RIGHT OUTER CROSS INNER);
sub parse_tree     { return shift->tree->tree }
sub tokens         { return shift->tree->tokens(@_) }

my $rule_class    = 'DBIx::MyParsePP::Rule';
my $token_class   = 'DBIx::MyParsePP::Token';

sub process {
      my ($self , $sel , $extract) = @_;
      
      if ($#{$extract} == 0) { #extract one called, we don't need to parse a tree
            my $sel_table = SQL::Builder::Table->new();
            # $extract could be table or alias
            my $tables = $self->tables->tables;
            if (grep { $_ eq $extract->[0] } @$tables) {
                  $sel_table->name($extract->[0]); # yes it's a table
            } else {
                  # it's an alias :)
                  my $table = $self->tables->table_by_alias($extract->[0]);
                  $sel_table->name($table);
                  $sel_table->alias($extract->[0]);
            }
            $sel->tables->list_push($sel_table);
      } else {
            my $from_tree = $self->parse_tree->extract('select_from');
            $from_tree = $self->shrink($from_tree);
            if ($from_tree) {
                  my $frmlist = SQL::Builder::FromList->new;
                  my $sql_builder_from_tree = $self->select_from( $from_tree );
                  $sel->tables($sql_builder_from_tree);
            }
      }
}

#RULES
sub select_from {
      my ($self, $rule) = @_;

      my @children = $rule->children;
      my $from = shift @children;
      die "something wrong"
            unless $from->isa( $token_class ) && lc($from->value) eq 'from';

      my $res = SQL::Builder::FromList->new;
      foreach my $c ( @children ) {
            die "don't know what to do with $c" unless $c->isa( $rule_class );
            
            my $method = $c->name;
            if ( $method eq 'derived_table_list' ) {
                  $self->$method( $c, $res );
            } elsif ($method eq 'join_table') {
                  $res->joins->list_push( $self->$method( $c ) );
            } elsif ($method =~ /(where|order|group)_clause/) {
                  next;
            }
            else {
                  #use Data::Dumper;
                  #print Dumper $rule;
                  
                  die "don't know how to treat '$method' rule in 'select_from' rule";
                  
                  
                  #we are getting 'where_clause' here, so we can't just die
            }
      }
      return $res;
}

sub derived_table_list {
      my ($self, $rule, $from_list) = @_;

      foreach my $c ( $rule->children ) {
            if ( $c->isa ( $rule_class ) ) {
                  my $method = $c->name;
                  if ( $method eq 'join_table' ) {
                        $from_list->joins->list_push( $self->$method( $c ) );
                  } else {
                        $from_list->tables->list_push( $self->$method( $c ) );
                        #die "don't know how to treat '$method' rule in 'derived_table_list' rule";
                        #otherwise it's just a table, not a join or set of joins
                  }
                  next;
            }
            die "don't know what to do with $c" unless $c->isa( $token_class );
            
            my $type = $c->type;
            if ( $type eq 'IDENT' ) {
                  my $table = SQL::Builder::FromTable->new();
                  $table->table($c->value);
                  $from_list->tables->list_push( $table );
            } elsif ( $type eq ',' ) {
                  next;
            } else {
                  die "don't know what to do with token '$type'";
            }
      }
}

sub join_table {
      my ($self, $rule) = @_;

      #my @children = $rule->children;

      #my $head = shift @children;
      #die "it's a not a 'table_factor' rule"
      #      unless $head->isa( $rule_class ) && $head->name eq 'table_factor';
      #$from_list->tables->list_push( $self->table_factor( $head ) );

      #print Dumper $rule;
#<<<<<<< .mine
      my @res;
      my @j_type = ();
      my $first = 1;
      my $join = SQL::Builder::Join->new();
      $join->right_table(''); # this is a bug in SQL::Builder::Join
      
      for my $c ( $rule->children ) {
            if ( $c->isa ( $rule_class ) ) {
                  my $method = $c->name;
                  if ($method eq 'table_factor') {
                        my $table = $self->$method( $c );
                        my $table_name = $table->table;
                        my $table_alias = $table->alias;
                        if ($first) {
                              if ($join->left_table) {
                                    $join->right_table( $table_name );
                                    $join->right_alias( $table_alias );
                              } else {
                                    $join->left_table( $table_name );
                                    $join->left_alias( $table_alias );
                              }
                              if ($join->left_table and $join->right_table) {
                                    $first = 0;
                              }
                        } else {
                              $join->right_table( $table_name );
                              $join->right_alias( $table_alias );
                        }
                  } elsif ($method eq 'bool_term' or $method eq 'bool_pri') {
                        my $on = $self->$method( $c );
                        $join->on($on);
                  } 
                  next;
                  
            }
            die "don't know what to do with $c" unless $c->isa( $token_class );
            
            my $type = $c->type;
            if ($type eq 'JOIN_SYM') {
                  #we need to go back for a JOIN type, how?
                  if (! $first) {
                        push @res , $join;
                        $join = SQL::Builder::Join->new();
                        $join->right_table('');
                        
                        $join->type(join " " , @j_type);
                        @j_type = ();
                        
                  } else {
                        $join->type(join " " , @j_type);
                        @j_type = ();
                  }
            } elsif ($type eq 'IDENT') { #table without alias
                  if ($first) {
                        if ($join->left_table) {
                              $join->right_table( $c->value );
                        } else {
                              $join->left_table( $c->value );
                        }
                        if ($join->left_table and $join->right_table) {
                              $first = 0;
                        }
                  } else {
                        $join->right_table( $c->value );
                  }
            } elsif (grep { $_ eq $type } @join_types) {
                  push @j_type , $type;
            }
      }
      
      push @res , $join;
      return @res;
#=======
      # here we have first problem
      # we should lookup everything up to JOIN_SYM token it will be join type

      
#>>>>>>> .r62
}

sub table_factor {
      my ($self, $rule) = @_;

      my $table = SQL::Builder::FromTable->new();
      foreach my $c ( $rule->children ) {
            if ( $c->isa ( $rule_class ) ) {
                  my $method = $c->name;
                  die "only opt_table_alias rule aloud inside table_factor"
                        if $method ne 'opt_table_alias';
                  $table->alias( $self->$method( $c ) );
                  next;
            }
            die "don't know what to do with $c" unless $c->isa( $token_class );
            
            my $type = $c->type;
            if ($type eq 'IDENT') {
                  if ($table->table) {
                        $table->alias($c->value);
                  } else {
                        $table->table($c->value);
                  }
            } 
      }
      return $table;
}

sub opt_table_alias {
      my ($self , $rule) = @_;
      
      foreach my $c ( $rule->children ) {
            die "can't be any rules inside alias rule" if $c->isa( $rule_class );
            
            my $type = $c->type;
            if ($type eq 'IDENT') {
                  return $c->value;
            }
      }
}

sub normal_join {
      my ($self , $rule) = @_;
      
      my @res = ();
      foreach my $c ( $rule->children ) {
            die "can't be any rules inside normal_join rule" if $c->isa( $rule_class );
            
            my $type = $c->type;
            if (grep { $_ eq $type } @join_types) {
                  push @res , $c->value;
            }     
      }      
      return @res;      
}

sub bool_term {
      my ($self , $rule) = @_;
      
      return $rule->toString();
      
      
      
      #should be improved
}

sub bool_pri {
      my ($self , $rule) = @_;
      
      return $rule->toString();
}

sub where_clause { }
sub group_clause { }
sub order_clause { }

sub shrink {
      my ($self, $parent, $no_descend) = @_;
      
      return $parent if $parent->isa( $token_class );
      return undef unless @$parent;

      if ( $#{$parent} == 0 ) {
            return undef; # shrink leafs
      }
      elsif ( $#{$parent} == 1 ) {
            my $child = $self->shrink( $parent->[1] );
            return $child;
      } elsif ( !$no_descend ) {
            
            if ($parent->name eq 'normal_join') { # this is the ugly way of dealing with the problem
                  my @c = ();
                  for (my $i = 1; $i <= $#{$parent}; $i++) {
                        push @c , $parent->[$i];
                  }
                  return @c;
            }
            
            my @new_children;
            foreach my $child ( grep defined, map $self->shrink( $_ ), @{$parent}[1..$#{$parent}] ) {
                  if ( $child->isa( $rule_class ) && $child->name eq $parent->name ) {
                        push @new_children, $child->children if defined $child->children;
                  } else {
                        push @new_children, $child if defined $child;
                  }
            }
            
            my $new_rule = $rule_class->new( $parent->name, @new_children );
            return $self->shrink( $new_rule, 'no descend' );  
            
      } else {
            return $parent;
      }
}

1;
